﻿using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Users;
using Xms.Abp.Core;
using Xms.Abp.Linq;

namespace Xms.Abp.Storage;

public class XmsStorageRepository<TStorage, TEntity> : EfCoreRepository<TStorage, TEntity>, IXmsStorageRepository<TEntity>
    where TStorage : IXmsStorage
    where TEntity : class, IEntity
{
    public virtual new IXmsStorageBulkOperationProvider BulkOperationProvider => base.BulkOperationProvider as IXmsStorageBulkOperationProvider;
    private IQueryableExecuter _QueryableExecuter;
    protected virtual IQueryableExecuter QueryableExecuter
    {
        get
        {
            if (_QueryableExecuter == null)
            {
                _QueryableExecuter = AsyncExecuter as IQueryableExecuter;
                if (_QueryableExecuter == null)
                {
                    _QueryableExecuter = LazyInject<IQueryableExecuter>();
                }
            }
            return _QueryableExecuter;
        }
    }
    public XmsStorageRepository(IDbContextProvider<TStorage> dbContextProvider) : base(dbContextProvider)
    {
    }

    public TService LazyInject<TService>()
    {
        return LazyServiceProvider.LazyGetRequiredService<TService>();
    }

    [Obsolete("Use LazyInject method.")]
    public TService Inject<TService>()
    {
        return ServiceProvider.GetRequiredService<TService>();
    }

    [Obsolete("Use AsNoTrackingAsync() method.")]
    public IQueryable<TEntity> AsNoTracking()
    {
        return IQueryableExtensions.AsNoTracking(DbSet);
    }

    public async Task<IQueryable<TEntity>> AsNoTrackingAsync()
    {
        return IQueryableExtensions.AsNoTracking(await GetDbSetAsync());
    }

    [Obsolete("Use AsTrackingAsync() method.")]
    public IQueryable<TEntity> AsTracking()
    {
        return IQueryableExtensions.AsTracking(DbSet);
    }

    public async Task<IQueryable<TEntity>> AsTrackingAsync()
    {
        return IQueryableExtensions.AsTracking(await GetDbSetAsync());
    }

    [Obsolete("Use AttachAsync() method.")]
    public void Attach(TEntity entity)
    {
        DbSet.Attach(entity);
    }

    [Obsolete("Use AttachAsync() method.")]
    public void Attach(IEnumerable<TEntity> entities)
    {
        DbSet.AttachRange(entities);
    }

    public async Task AttachAsync(TEntity entity)
    {
        (await GetDbSetAsync()).Attach(entity);
    }

    public async Task AttachAsync(IEnumerable<TEntity> entities)
    {
        (await GetDbSetAsync()).AttachRange(entities);
    }

    public async Task DeleteManyAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.DeleteManyAsync<TStorage, TEntity>(
                this,
                predicate,
                autoSave,
                cancellationToken
            );

            return;
        }

        var dbContext = await GetDbContextAsync();

        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        dbContext.RemoveRange(entities);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task UpdateManyAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TEntity>> updateFactory, Action<TEntity> updateAction, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.UpdateManyAsync<TStorage, TEntity>(
                this,
                predicate,
                updateFactory,
                autoSave,
                cancellationToken
                );

            return;
        }

        var dbContext = await GetDbContextAsync();
        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        foreach (var entity in entities)
        {
            updateAction(entity);
        }

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task UpdateManyAsync(Expression<Func<TEntity, bool>> predicate, Dictionary<string, object> values, Action<TEntity> updateAction, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.UpdateManyAsync<TStorage, TEntity>(
                this,
                predicate,
                values,
                autoSave,
                cancellationToken
                );

            return;
        }

        var dbContext = await GetDbContextAsync();
        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        foreach (var entity in entities)
        {
            updateAction(entity);
        }

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task<IQueryable<TEntity>> IncludeAsync<TProperty>(Expression<Func<TEntity, TProperty>> navigationPropertyPath)
    {
        return EntityFrameworkQueryableExtensions.Include(await GetDbSetAsync(), navigationPropertyPath);
    }

    public async Task<IQueryable<TEntity>> IncludeAsync<TProperty>(string navigationPropertyPath)
    {
        return EntityFrameworkQueryableExtensions.Include(await GetDbSetAsync(), navigationPropertyPath);
    }

    public async Task<IQueryable<TEntity>> GetQueryAsync(string sql, params object[] parameters)
    {
        return (await GetDbSetAsync()).FromSqlRaw(sql, parameters);
    }
}
public class XmsStorageRepository<TStorage, TEntity, TKey> : EfCoreRepository<TStorage, TEntity, TKey>, IXmsStorageRepository<TEntity, TKey>
    where TStorage : IXmsStorage
    where TEntity : class, IEntity<TKey>
{
    public virtual new IXmsStorageBulkOperationProvider BulkOperationProvider => base.BulkOperationProvider as IXmsStorageBulkOperationProvider;
    private IQueryableExecuter _QueryableExecuter;
    protected virtual IQueryableExecuter QueryableExecuter
    {
        get
        {
            if (_QueryableExecuter == null)
            {
                _QueryableExecuter = AsyncExecuter as IQueryableExecuter;
                if (_QueryableExecuter == null)
                {
                    _QueryableExecuter = LazyInject<IQueryableExecuter>();
                }
            }
            return _QueryableExecuter;
        }
    }
    protected virtual ICurrentUser CurrentUser => LazyInject<ICurrentUser>();

    public TService LazyInject<TService>()
    {
        return LazyServiceProvider.LazyGetRequiredService<TService>();
    }

    [Obsolete("Use LazyInject method.")]
    public TService Inject<TService>()
    {
        return ServiceProvider.GetRequiredService<TService>();
    }

    public XmsStorageRepository(IDbContextProvider<TStorage> dbContextProvider) : base(dbContextProvider)
    {
    }

    [Obsolete("Use AsNoTrackingAsync() method.")]
    public IQueryable<TEntity> AsNoTracking()
    {
        return IQueryableExtensions.AsNoTracking(DbSet);
    }

    public async Task<IQueryable<TEntity>> AsNoTrackingAsync()
    {
        return IQueryableExtensions.AsNoTracking(await GetDbSetAsync());
    }

    [Obsolete("Use AsTrackingAsync() method.")]
    public IQueryable<TEntity> AsTracking()
    {
        return IQueryableExtensions.AsTracking(DbSet);
    }

    public async Task<IQueryable<TEntity>> AsTrackingAsync()
    {
        return IQueryableExtensions.AsTracking(await GetDbSetAsync());
    }

    [Obsolete("Use AttachAsync() method.")]
    public void Attach(TEntity entity)
    {
        DbSet.Attach(entity);
    }

    [Obsolete("Use AttachAsync() method.")]
    public void Attach(IEnumerable<TEntity> entities)
    {
        DbSet.AttachRange(entities);
    }

    public async Task AttachAsync(TEntity entity)
    {
        (await GetDbSetAsync()).Attach(entity);
    }

    public async Task AttachAsync(IEnumerable<TEntity> entities)
    {
        (await GetDbSetAsync()).AttachRange(entities);
    }

    public async Task DeleteManyAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.DeleteManyAsync<TStorage, TEntity>(
                this,
                predicate,
                autoSave,
                cancellationToken
            );

            return;
        }

        var dbContext = await GetDbContextAsync();

        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        dbContext.RemoveRange(entities);

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task UpdateManyAsync(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TEntity>> updateFactory, Action<TEntity> updateAction, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.UpdateManyAsync<TStorage, TEntity>(
                this,
                predicate,
                updateFactory,
                autoSave,
                cancellationToken
                );

            return;
        }

        var dbContext = await GetDbContextAsync();
        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        foreach (var entity in entities)
        {
            updateAction(entity);
        }

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task UpdateManyAsync(Expression<Func<TEntity, bool>> predicate, Dictionary<string, object> values, Action<TEntity> updateAction, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        cancellationToken = GetCancellationToken(cancellationToken);

        if (BulkOperationProvider != null)
        {
            await BulkOperationProvider.UpdateManyAsync<TStorage, TEntity>(
                this,
                predicate,
                values,
                autoSave,
                cancellationToken
                );

            return;
        }

        var dbContext = await GetDbContextAsync();
        var entities = await EntityFrameworkQueryableExtensions.ToArrayAsync(dbContext.Set<TEntity>().Where(predicate), cancellationToken);
        foreach (var entity in entities)
        {
            updateAction(entity);
        }

        if (autoSave)
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
    }

    public async Task<IQueryable<TEntity>> IncludeAsync<TProperty>(Expression<Func<TEntity, TProperty>> navigationPropertyPath)
    {
        return EntityFrameworkQueryableExtensions.Include(await GetDbSetAsync(), navigationPropertyPath);
    }

    public async Task<IQueryable<TEntity>> IncludeAsync<TProperty>(string navigationPropertyPath)
    {
        return EntityFrameworkQueryableExtensions.Include(await GetDbSetAsync(), navigationPropertyPath);
    }

    public async Task<IQueryable<TEntity>> GetQueryAsync(string sql, params object[] parameters)
    {
        return (await GetDbSetAsync()).FromSqlRaw(sql, parameters);
    }

    public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
    {
        var ents = entities.OfType<IModificationAuditedObject>();
        foreach (var entity in ents)
        {
            entity.LastModificationTime = DateTime.Now;
            entity.LastModifierId = CurrentUser.Id;
        }
        await base.InsertManyAsync(entities, autoSave, cancellationToken);
    }
}
